#include "./factory/factory.hh"
#include "./circuit/circuit.hh"
#include "./util/util.hh"

#include <cstddef>
#include <cstdio>
#include <exception>
#include <memory>
#include <tuple>
#include <format>
#include <fstream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <vector>
#include <filesystem>

namespace ycspice::preprocess {

    static auto clean_input_content(const std::filesystem::path& origin_file_path) -> std::vector<std::string> {
        // std::println("Clean input spice content");
        
        // delete empty line & delete comment * to_lowecase 
        auto read_lines = read_file_lines(origin_file_path);
        auto new_lines = std::vector<std::string>{};
        for (auto& read_line : read_lines) {
            auto trim_line = string_trim(read_line);
            if (trim_line.empty() || trim_line.starts_with('*')) {
                continue;
            }

            new_lines.emplace_back(string_lowercase(trim_line));
        }

        return new_lines;    
    } 

    static auto collect_subckt_infos(std::vector<std::string> origin_lines) -> std::tuple<std::vector<std::string>, std::unique_ptr<Factory>> {
        // std::println("Collect the .SUBCKT Infos");

        auto inst_lines = std::vector<std::string>{};
        auto factory = std::make_unique<Factory>();

        auto iter = origin_lines.begin();
        while (iter != origin_lines.end()) {
            auto line = *iter;

            if (line.starts_with(".subckt")) {
                auto subckt_info = SubcktInfo{};
                auto tokens = string_split_space(line);
                if (tokens.size() < 3) {
                    throw std::runtime_error{std::format("Bad input '{}'", *iter)};
                }

                subckt_info.name = std::string{tokens[1]};
                for (std::size_t i = 2; i < tokens.size(); ++i) {
                    subckt_info.ports.emplace_back(std::string{tokens[i]});
                }

                while (true) {
                    ++iter;
                    if (iter == origin_lines.end()) {
                        throw std::runtime_error{std::format("No '.ends' for .subckt '{}'", tokens[1])};
                    }
                    if (iter->starts_with(".ends")) {
                        ++iter;
                        break;
                    }

                    // inst line!
                    subckt_info.insts.emplace_back(std::move(*iter));
                }

                // std::println("Add a circuit '{}'", subckt_info.name);
                factory->emplace_subckt_info(std::string(tokens[1]), std::move(subckt_info));   
            } else {
                inst_lines.emplace_back(std::move(*iter));
                ++iter;
            }
        }

        return std::make_tuple(std::move(inst_lines), std::move(factory));
    }

    static auto create_and_expand_subckt(
        const std::filesystem::path& origin_file_path, 
        std::vector<std::string> inst_lines, 
        std::unique_ptr<Factory> factory) -> std::filesystem::path
    {
        // std::println("Build circuit and expand .SUBCKT");

        // Build circuit!
        auto processed_file_path = std::format("{}.expand.sp", origin_file_path.string());
        auto outfile = std::ofstream{processed_file_path};
        for (auto& line : inst_lines) {
            if (line.starts_with('x')) {
                auto tokens = string_split_space(line);
                // std::println("expand a instance '{}' of subckt '{}'", tokens.front(), tokens.back());

                if (tokens.size() < 3) {
                    throw std::runtime_error{std::format("Bad input '{}'", line)};
                }

                auto inst_name = tokens[0];
                auto circuit_name = tokens.back();
                auto nets = std::vector<std::string>{};
                for (std::size_t i = 1; i < tokens.size() - 1; ++i) {
                    nets.emplace_back(tokens[i]);
                }

                auto circuit = factory->create_subckt(circuit_name);

                outfile << circuit->expand_spice(inst_name, nets) << '\n';

            } else {
                outfile << line << '\n';
            }
        }

        return processed_file_path;
    }

    auto expand_subckt(const std::filesystem::path& origin_file_path) -> std::filesystem::path {
        auto origin_lines = clean_input_content(origin_file_path);

        auto [inst_lines, factory] = collect_subckt_infos(std::move(origin_lines));
        
        return create_and_expand_subckt(origin_file_path, std::move(inst_lines), std::move(factory));
    }

    extern "C" {
        // Wrap the C++ function with a C-compatible interface
        const char* expand_subckt_c(const char* origin_file_path) {
            try {
                // Call the original C++ function
                static std::string result = expand_subckt(std::filesystem::path(origin_file_path)).string();
                return result.c_str(); // Return the path as a C string
            } catch (const std::exception& err) {
                std::printf("Err: %s\n", err.what());
                return nullptr; // Handle errors gracefully
            }
        }
    }

}